#include "GfxDriver/WGL/WGLShader.h"
#include "GfxDriver/GfxDriver.h"
#include "Core/LogService.h"

namespace Orca {

	//--------------------------------------------------------------------------
	// WGLVertexProgram
	//--------------------------------------------------------------------------
	WGLVertexProgram* WGLVertexProgram::create(
		 GfxDriver *gfxDriver
		,const uint8* data) {

		gfxDriver->resetErrors();

		GLchar glMsgBuf[512];
		GLsizei glMsgLen;
		GLint glResult;

		GLuint glHandle = glCreateShader(GL_VERTEX_SHADER);

		// vertex shader
		glShaderSource(glHandle, 1, (const char**)&data, NULL);
		glCompileShader(glHandle);
		glGetShaderiv(glHandle, GL_COMPILE_STATUS, &glResult);

		if( GL_FALSE == glResult ) {

			glGetShaderInfoLog(glHandle, 512, &glMsgLen, glMsgBuf);

			LOG_ERR(
				"WGLGfxDriver: failed to compile vertex program\n"
				"\tReason: " + Str(glMsgBuf) );

			return NULL;
		}

		return new WGLVertexProgram(glHandle);
	}

	//--------------------------------------------------------------------------
	WGLVertexProgram::WGLVertexProgram(GLuint glHandle)
		: mGLHandle(glHandle) {

	}

	//--------------------------------------------------------------------------
	WGLVertexProgram::~WGLVertexProgram() {

		glDeleteShader(mGLHandle);
	}

	//--------------------------------------------------------------------------
	unsigned int WGLVertexProgram::getAPIHandle() {

		return mGLHandle;
	}

	//--------------------------------------------------------------------------
	// WGLFragmentProgram
	//--------------------------------------------------------------------------
	WGLFragmentProgram* WGLFragmentProgram::create(
		 GfxDriver *gfxDriver
		,const uint8* data) {

		gfxDriver->resetErrors();

		GLchar glMsgBuf[512];
		GLsizei glMsgLen;
		GLint glResult;

		GLuint glHandle = glCreateShader(GL_FRAGMENT_SHADER);

		// fragment shader
		glShaderSource(glHandle, 1, (const char**)&data, NULL);
		glCompileShader(glHandle);
		glGetShaderiv(glHandle, GL_COMPILE_STATUS, &glResult);

		if( GL_FALSE == glResult ) {

			glGetShaderInfoLog(glHandle, 512, &glMsgLen, glMsgBuf);

			LOG_ERR(
				"WGLGfxDriver: failed to compile fragment program\n"
				"\tReason: " + Str(glMsgBuf) );

			return NULL;
		}

		return new WGLFragmentProgram(glHandle);
	}

	//--------------------------------------------------------------------------
	WGLFragmentProgram::WGLFragmentProgram(GLuint glHandle)
		: mGLHandle(glHandle) {

	}

	//--------------------------------------------------------------------------
	WGLFragmentProgram::~WGLFragmentProgram() {

		glDeleteShader(mGLHandle);
	}

	//--------------------------------------------------------------------------
	unsigned int WGLFragmentProgram::getAPIHandle() {

		return mGLHandle;
	}

	//--------------------------------------------------------------------------
	// WGLShader
	//--------------------------------------------------------------------------
	WGLShader* WGLShader::create(
		 GfxDriver *gfxDriver
		,VertexProgram *vp
		,FragmentProgram *fp) {

		gfxDriver->resetErrors();

		if(!vp || !fp) {

			LOG_ERR("WGLGfxDriver: failed create shader, invalid vp/fp");
			return NULL;
		}

		// link shaders to program
		GLuint progHandle = glCreateProgram();

		glAttachShader( progHandle, (GLuint)vp->getAPIHandle() );
		glAttachShader( progHandle, (GLuint)fp->getAPIHandle() );

		glLinkProgram(progHandle);

		if( GL_NO_ERROR != glGetError() ) {

			LOG_ERR("WGLGfxDriver: failed to link program");
			
			glDeleteProgram(progHandle);

			return NULL;
		}

		// return shader object
		WGLShader *shader = new WGLShader(gfxDriver, progHandle);

		LOG_MSG("WGLGfxDriver: Shader created; glHandle=" + toStr((uint32)progHandle) );

		return shader;
	}

	//--------------------------------------------------------------------------
	WGLShader::WGLShader(GfxDriver *gfxDriver, GLuint glHandle)
		: mGfxDriver(gfxDriver)
		, mGLHandle(glHandle) {

		glUseProgram(mGLHandle);

		GLint uniformCnt;
		glGetProgramiv(mGLHandle, GL_ACTIVE_UNIFORMS, &uniformCnt);

		uint32 samplerCnt = 0;
		
		for(int i=0; i<uniformCnt; ++i) {

			GLchar name[32];
			GLsizei nameLength;
			GLint size;
			GLenum type;

			glGetActiveUniform(glHandle, i, 32, &nameLength, &size, &type, name);

			if( GL_NO_ERROR == glGetError() ) {

				switch(type) {

					case GL_SAMPLER_1D:
					case GL_SAMPLER_2D:
					case GL_SAMPLER_3D:
					case GL_SAMPLER_CUBE:
					case GL_SAMPLER_1D_SHADOW:
					case GL_SAMPLER_2D_SHADOW:
						{
							Str strName(name);

							mSamplerMapping.insert( std::make_pair(strName, samplerCnt ) );
							mSamplerTexture.push_back(NULL);

							glUniform1i( _getUniformLocation(strName), (GLint)samplerCnt );
							//printf("%d %s %x %d\n", i, name, type, samplerCnt);
							samplerCnt++;
							break;
						}
					default:
						break;

				} // end switch

			} // end if( GL_NO_ERROR == glGetError() )

		} // for(int i=0; i<uniformCnt; ++i)

		// unbind program
		glUseProgram(0);
	}

	//--------------------------------------------------------------------------
	WGLShader::~WGLShader() {

		glDeleteProgram(mGLHandle);
	}

	//--------------------------------------------------------------------------
	void WGLShader::beginRender() {

		glUseProgram(mGLHandle);

		for(uint32 i=0; i<mSamplerTexture.size(); ++i) {

			mGfxDriver->bindTexture(i, mSamplerTexture[i]);
		}
	}

	//--------------------------------------------------------------------------
	void WGLShader::endRender() {

		glUseProgram(0);
	}

	//--------------------------------------------------------------------------
	void WGLShader::beginBinding() {

		glUseProgram(mGLHandle);
	}

	//--------------------------------------------------------------------------
	void WGLShader::endBinding() {

		glUseProgram(0);
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindFloat(const Str &name, float val) {

		glUniform1f( _getUniformLocation(name), val );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindVec2(const Str &name, const vec2 &val) {

		glUniform2f( _getUniformLocation(name), val[0], val[1] );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindVec3(const Str &name, const vec3 &val) {

		glUniform3f( _getUniformLocation(name), val[0], val[1], val[2] );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindVec4(const Str &name, const vec4 &val) {

		glUniform4f( _getUniformLocation(name), val[0], val[1], val[2], val[3] );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindMtx4(const Str &name, const mtx44 &val) {

		glUniformMatrix4fv( _getUniformLocation(name), 1, GL_FALSE, val.getData() );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindFloatArray(const Str &name, const float *val, uint32 cnt) {

		glUniform1fv( _getUniformLocation(name), (GLsizei)cnt, val);
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindVec2Array(const Str &name, const vec2 *val, uint32 cnt) {

		glUniform2fv( _getUniformLocation(name), (GLsizei)cnt, (float*)val );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindVec3Array(const Str &name, const vec3 *val, uint32 cnt) {

		glUniform3fv( _getUniformLocation(name), (GLsizei)cnt, (float*)val );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindVec4Array(const Str &name, const vec4 *val, uint32 cnt) {

		glUniform4fv( _getUniformLocation(name), (GLsizei)cnt, (float*)val );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindMtx4Array(const Str &name, const mtx44 *val, uint32 cnt) {

		glUniformMatrix4fv( _getUniformLocation(name), (GLsizei)cnt, GL_FALSE, (float*)val );
	}

	//--------------------------------------------------------------------------
	void WGLShader::bindTexture(const Str &name, Texture *tex) {

		TSamplerMapping::iterator iter = mSamplerMapping.find(name);

		if( iter != mSamplerMapping.end() ) {

			mSamplerTexture[iter->second] = tex;
		}

	}

	//--------------------------------------------------------------------------
	GLint WGLShader::_getUniformLocation(const Str &name) {

		GLint loc = glGetUniformLocation(mGLHandle, name.c_str());
		return loc;
	}

	//--------------------------------------------------------------------------

}